package g8

import (
	"github.com/nats-io/nats.go"
	"go.uber.org/zap"
	"google.golang.org/protobuf/proto"
	"strconv"
	"sync"
	"time"
	"wzgames/internal/codec/sfs"
	"wzgames/internal/constant"
	"wzgames/internal/gloader"
	"wzgames/internal/model/dwp"
	"wzgames/internal/model/login"
	"wzgames/internal/services/dila"
	"wzgames/internal/services/mdc"
	"wzgames/pkg/actor/actor"
	"wzgames/pkg/actor/actorservice"
	"wzgames/pkg/common/timingwheel"
	"wzgames/pkg/logger"
	"wzgames/pkg/server/ws"
)

var (
	wsGuard *WsGuard
	oncew   sync.Once
)

type WsGuard struct {
	actorservice.ServiceMeta
	ws *ws.WebSocket
	mu sync.RWMutex

	hallAgents map[int64]*mdc.HallAgent

	msgSub       *nats.Subscription
	keyKickCheck string
	keyBalance   string

	timerWheel *timingwheel.TimingWheel
	t1         time.Duration
}

func Actor() actorservice.IService {
	if wsGuard == nil {
		oncew.Do(func() {
			wsGuard = &WsGuard{
				hallAgents: make(map[int64]*mdc.HallAgent),
				timerWheel: timingwheel.NewTimingWheel(time.Second, 60),
				t1:         time.Minute,
			}
			wsGuard.Name = "actor_ws_guard_g8"
		})
	}
	return wsGuard
}

func (wg *WsGuard) OnReceive(ctx actor.Context) {
	switch m := ctx.Message().(type) {
	case *ws.MsgEvent:
		wg.sysControlMessage(m)
	case *login.LoginGame:
		wg.loginGame(m)
	default:
	}
}

func (wg *WsGuard) OnStart(as *actorservice.ActorService) {
	if wg.ws != nil {
		wg.ws.SetReceiver(mdc.NewWsReceiver(wg.GetPID()))
	}
	wg.timerWheel.Start()
	dila.Init(gloader.BootConfig().Node().NodeId, wg.timerWheel)
	wg.checkPlayerActive(nil)

	nodeIdStr := strconv.FormatInt(gloader.BootConfig().Node().NodeId, 10)
	wg.keyKickCheck = constant.LoginCheckNKey + "." + nodeIdStr
	wg.keyBalance = constant.BalanceNKey + "." + nodeIdStr
	sub1, err := gloader.CommMgr().NatsC().Subscribe(constant.NodeMsgNKeySub+"."+nodeIdStr, wg.handleMsg)
	if err != nil {
		logger.Error("[G8] subscribe msg fail", zap.String("SubKey", constant.NodeMsgNKeySub), zap.Error(err))
		return
	}
	wg.msgSub = sub1
}

func (wg *WsGuard) Attach(a interface{}) {
	wg.SuperV = a
	v, ok := a.(*ws.WebSocket)
	if !ok {
		panic("invalid attach type")
	}
	wg.ws = v
}

func (wg *WsGuard) OnDestroy() {
	if wg.msgSub != nil {
		_ = wg.msgSub.Unsubscribe()
		wg.msgSub.Drain()
	}
	dila.Stop()
	wg.timerWheel.Stop()
	actorservice.Stop(wg.GetPID())
}

func (wg *WsGuard) sysControlMessage(req *ws.MsgEvent) {
	defer logger.Recover()

	switch m := req.Message().(type) {
	case *sfs.TSfsObject:
		switch m.GetID() {
		case constant.ActionHandShake:
			mdc.GetReceiver().HandleHandShake(req.Session(), m)
		case constant.ActionLogin:
			mdc.GetReceiver().HandleLogin(req.Session(), m)
		default:
			logger.Warn("[G8] illegal action id", zap.Uint16("ActionID", m.GetID()))
		}
	}
}

func (wg *WsGuard) loginGame(data *login.LoginGame) {
	defer logger.Recover()
	if data.Reply.LastLoginNodeId > 0 && data.Reply.LastLoginNodeId != gloader.BootConfig().Node().NodeId {
		wg.sendKickCheck(data.Reply.UserCache.AgtId, data.GameInstId, data.Reply.LastLoginNodeId, data.Reply.UserCache.Uid)
	}
	wg.LoadHallAgent(data.Reply.UserCache.AgtId, data.Reply.AgtKey).LoginGame(GetSession(data.SessID), data)
}

func (wg *WsGuard) LoadHallAgent(agtId int64, agtKey string) *mdc.HallAgent {
	wg.mu.RLock()
	agtHall, ok := wg.hallAgents[agtId]
	wg.mu.RUnlock()
	if ok {
		return agtHall
	}

	wg.mu.Lock()
	defer wg.mu.Unlock()
	agtHall = mdc.NewHallAgent(agtId, agtKey)
	wg.hallAgents[agtId] = agtHall

	return agtHall
}

func (wg *WsGuard) checkPlayerActive(args interface{}) {
	wg.mu.RLock()
	for _, agt := range wg.hallAgents {
		agt.CheckPlayerActive()
	}
	wg.mu.RUnlock()
	wg.timerWheel.AfterFunc(wg.t1, wg.checkPlayerActive, nil)
}

func (wg *WsGuard) handleMsg(msg *nats.Msg) {
	switch msg.Subject {
	case wg.keyKickCheck:
		wg.checkKickCheck(msg.Data)
	case wg.keyBalance:
		wg.balanceChange(msg.Data)
	}
}

func (wg *WsGuard) sendKickCheck(agtId, instId, nodeId int64, uid uint64) {
	obj := &login.LoginKickCheck{
		AgtID:    agtId,
		Uid:      uid,
		InstId:   instId,
		NodeId:   nodeId,
		FromNode: gloader.BootConfig().Node().NodeId,
	}
	b, err := proto.Marshal(obj)
	if err != nil {
		logger.Error("[G8] send kick fail on proto.Marshal", zap.Int64("AgtId", agtId), zap.Uint64("Uid", uid), zap.Int64("InstID", instId), zap.Error(err))
		return
	}
	_ = gloader.CommMgr().NatsC().Publish(wg.keyKickCheck, b)
}

func (wg *WsGuard) checkKickCheck(data []byte) {
	obj := &login.LoginKickCheck{}
	err := proto.Unmarshal(data, obj)
	if err != nil {
		logger.Error("[G8] check kick fail on proto.Unmarshal", zap.Error(err))
		return
	}
	if obj.FromNode == gloader.BootConfig().Node().NodeId || obj.NodeId != gloader.BootConfig().Node().NodeId {
		return
	}
	wg.mu.RLock()
	agtHall, ok := wg.hallAgents[obj.AgtID]
	wg.mu.RUnlock()
	if ok {
		agtHall.CheckKickCheck(obj)
	}
}

func (wg *WsGuard) balanceChange(data []byte) {
	obj := &dwp.BalanceChange{}
	err := proto.Unmarshal(data, obj)
	if err != nil {
		logger.Error("[G8] check balance change fail on proto.Unmarshal", zap.Error(err))
		return
	}
	wg.mu.RLock()
	agtHall, ok := wg.hallAgents[obj.AgtID]
	wg.mu.RUnlock()
	if ok {
		agtHall.CheckPlayerBalanceChange(obj)
	}
}

func GetSession(sessId int64) ws.ISession {
	return wsGuard.ws.GetSession(sessId)
}
